/*
 * Copyright (C) 2016, apexes.net. All rights reserved.
 * 
 *        http://www.apexes.net
 * 
 */
package net.apexes.wsonrpc.core;

import net.apexes.wsonrpc.core.exception.JsonException;
import net.apexes.wsonrpc.core.exception.RemoteException;
import net.apexes.wsonrpc.json.JsonImplementor;
import net.apexes.wsonrpc.json.JsonRpcMessage;
import net.apexes.wsonrpc.json.JsonRpcRequest;
import net.apexes.wsonrpc.json.JsonRpcResponse;
import net.apexes.wsonrpc.util.JsonRpcErrors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

/**
 * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
 */
public class JsonRpcEngine {
    private static final Logger LOG = LoggerFactory.getLogger(JsonRpcEngine.class);

    private final JsonImplementor jsonImpl;
    private final BinaryWrapper binaryWrapper;
    private final ServiceRegistry serviceRegistry;
    private JsonRpcLogger jsonRpcLogger;

    public JsonRpcEngine(JsonImplementor jsonImpl) {
        this(jsonImpl, null);
    }

    public JsonRpcEngine(JsonImplementor jsonImpl, BinaryWrapper binaryWrapper) {
        if (jsonImpl == null) {
            throw new NullPointerException("jsonImpl");
        }
        this.jsonImpl = jsonImpl;
        this.binaryWrapper = binaryWrapper;
        this.serviceRegistry = new ServiceRegistry();
    }

    /**
     * @return 返回 {@link JsonImplementor} 对象
     */
    protected JsonImplementor getJsonImplementor() {
        return jsonImpl;
    }

    public ServiceRegistry getServiceRegistry() {
        return serviceRegistry;
    }

    public JsonRpcLogger getJsonRpcLogger() {
        return jsonRpcLogger;
    }

    public void setJsonRpcLogger(JsonRpcLogger jsonRpcLogger) {
        this.jsonRpcLogger = jsonRpcLogger;
    }

    /**
     * 远程调用方法。
     *
     * @param method 服务方法
     * @param args 参数
     * @param id 请求ID
     * @param transport {@link Transport}实例
     */
    public void invoke(String method, Object[] args, String id, Transport transport, String dest) throws IOException {
        transmit(transport, createRequest(method, args, id), dest);
    }

    public void invokeNotice(String method, Object[] args, String trace, Transport transport, String dest) throws IOException {
        transmit(transport, createNotice(method, args, trace), dest);
    }

    protected JsonRpcRequest createRequest(String method, Object[] args, String id) {
        return createRequest(method, args, id, null);
    }

    protected JsonRpcRequest createNotice(String method, Object[] args, String trace) {
        return createRequest(method, args, null, trace);
    }

    private JsonRpcRequest createRequest(String method, Object[] args, String id, String trace) {
        if (method == null) {
            throw new NullPointerException("method");
        }

        if (id != null) {
            return jsonImpl.createRequest(id, method, args);
        }
        return jsonImpl.createNotice(method, args, trace);
    }

    /**
     * 接收远端的调用请求，并将回复执行结果。
     *
     * @param bytes     接收到的数据
     * @param transport {@link Transport} 实例
     * @param from 来源
     */
    public void receiveRequest(byte[] bytes, Transport transport, String from)
            throws IOException, JsonException {
        JsonRpcMessage resp;
        try {
            JsonRpcMessage msg = parseOnReceive(bytes, from);
            if (msg.isRequest()) {
                resp = execute((JsonRpcRequest) msg);
            } else {
                resp = jsonImpl.createResponse(JsonRpcErrors.invalidRequestError());
            }
        } catch (JsonException e) {
            resp = jsonImpl.createResponse(JsonRpcErrors.parseError(e));
        } catch (IOException e) {
            resp = jsonImpl.createResponse(JsonRpcErrors.internalError(e));
        }
        transmit(transport, resp, from);
    }

    /**
     * 接收远程调用得到的回复，从回复中返回指定类型的对象。
     *
     * @param bytes      接收到的字节数组
     * @param returnType 返回的对象类型
     * @param from 来源
     * @return 返回指定类型的对象
     * @throws IOException IO错误
     * @throws JsonException 解释JSON抛出异常
     * @throws RemoteException  远程方法抛出异常
     */
    public <T> T receiveResponse(byte[] bytes, Type returnType, String from)
            throws IOException, JsonException, RemoteException {
        JsonRpcMessage msg;
        try {
            msg = parseOnReceive(bytes, from);
        } catch (JsonException e) {
            throw new JsonException("parse response error", e);
        }
        if (msg instanceof JsonRpcResponse) {
            return fromResponse((JsonRpcResponse) msg, returnType);
        } else {
            throw new JsonException("invalid response");
        }
    }

    /**
     * 处理远端的调用请求，执行相应的方法并返回执行结果。
     *
     * @param request 入参对象
     * @return 如果request为通知将返回 null
     */
    protected JsonRpcResponse execute(JsonRpcRequest request) {
        if (request == null) {
            return jsonImpl.createResponse(JsonRpcErrors.parseError());
        }

        String id = request.getId();
        String method = request.getMethod();

        ServiceMethodInvoker invoker = serviceRegistry.getInvoker(method);
        if (invoker == null) {
            return jsonImpl.createResponse(id, JsonRpcErrors.methodNotFoundError("Method not found : " + method));
        }

        try {
            Object invokeValue = invoker.invoke(request);

            if (id == null) {
                return null;
            }

            return jsonImpl.createResponse(id, invokeValue);
        } catch (ServiceMethodInvoker.ParamException e) {
            return jsonImpl.createResponse(id, JsonRpcErrors.invalidParamsError("Invalid params : " + method));
        } catch (Throwable t) {
            Throwable throwable = t;
            if (throwable instanceof InvocationTargetException) {
                throwable = ((InvocationTargetException) throwable).getTargetException();
            }
            String msg = "executing error : " + method;
            LOG.debug(msg, throwable);
            return jsonImpl.createResponse(id, JsonRpcErrors.serverError(2, msg, throwable));
        }
    }

    @SuppressWarnings("unchecked")
    protected <T> T fromResponse(JsonRpcResponse resp, Type returnType) throws JsonException {
        if (resp.getError() != null) {
            throw RemoteException.create(resp.getError());
        }

        Object resultValue = resp.getResultValue(returnType);
        if (resultValue == null) {
            return null;
        }

        try {
            return (T) resultValue;
        } catch (Throwable t) {
            throw new JsonException(t.getMessage(), t);
        }
    }

    protected JsonRpcMessage parseOnReceive(byte[] bytes, String from) throws IOException, JsonException {
        JsonRpcLogger logger = jsonRpcLogger;
        return parseOnReceive(bytes, logger, from);
    }

    protected JsonRpcMessage parseOnReceive(byte[] bytes, JsonRpcLogger logger, String from) throws IOException, JsonException {
        if (binaryWrapper != null) {
            LOG.debug(" - {}", bytes.length);
            bytes = binaryWrapper.read(bytes);
            LOG.debug(" = {}", bytes.length);
        }

        String json = new String(bytes, StandardCharsets.UTF_8);
        return parseOnReceive(json, logger, from);
    }

    protected JsonRpcMessage parseOnReceive(String json, String from) throws IOException, JsonException {
        JsonRpcLogger logger = jsonRpcLogger;
        return parseOnReceive(json, logger, from);
    }

    protected JsonRpcMessage parseOnReceive(String json, JsonRpcLogger logger, String from) throws IOException, JsonException {
        if (logger != null) {
            logger.onReceive(from, json);
        } else {
            LOG.debug(" << [{}] {}", from, json);
        }

        return jsonImpl.fromJson(json);
    }

    protected void transmit(Transport transport, JsonRpcMessage message, String dest)throws IOException {
        JsonRpcLogger logger = jsonRpcLogger;
        transmit(transport, message, logger, dest);
    }

    protected void transmit(Transport transport, JsonRpcMessage message, JsonRpcLogger logger, String dest) throws IOException {
        String json = message.toJson();

        if (logger != null) {
            logger.onTransmit(dest, json);
        } else {
            LOG.debug(" >> [{}] {}", dest, json);
        }

        byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
        if (binaryWrapper != null) {
            LOG.debug(" = {}", bytes.length);
            bytes = binaryWrapper.write(bytes);
            LOG.debug(" - {}", bytes.length);
        }
        transport.sendBinary(bytes);
    }

}
